package GameServer;

import Library.*;
import Library.Game.*;
import Library.Login.*;
import java.io.IOException;
import java.rmi.*;
import java.util.*;
import java.net.InetAddress;
import java.sql.SQLException;
import java.util.Map.*;

/**
 * GameServer
 * Het GameServer-object is de basis van de GameServer-Applicatie.
 * Dit object zorgt ervoor dat Games kunnen worden gecreeerd en worden
 * opgevraagd.
 */
public class GameServer extends RMIServer
{
    /**
     *
     */
    protected final transient List<IGame> m_Games = Collections.synchronizedList(new ArrayList<IGame>());
    /**
     *
     */
    protected transient Gateway m_Gateway;
    /**
     *
     */
    protected transient static Random m_Random = new Random();
    /**
     *
     */
    protected transient Database m_Database;
    
    /**
     * Genereert een random nummer.
     * Om Nummers echt random te maken moeten we een globale random gebruiken.
     * @return random nr
     */
    protected static int getRandomNr()
    {
        return m_Random.nextInt();
    }
    
    /**
     * Zorgt dat een collectie echt random geshuffled wordt.
     * @param collection ongeshufflde collectie
     */
    protected static void shuffleCollection(List<?> collection)
    {
        Collections.shuffle(collection, m_Random);
    }
    
    /**
     * Maakt een nieuw sessie-object aan met behulp van een port.
     * @throws RemoteException
     * @throws SQLException
     * @throws IOException
     */
    public GameServer() throws RemoteException,
            SQLException, IOException
    {
        super(Main.getSettings().getPort());
        m_Gateway = new Gateway();
        m_Database = new Database(Main.getSettings().getConnectionString(), Main.getSettings().getUsername(), Main.getSettings().getPassword());
    }
    //TODO: [GAMESERVER#07] [FUTURE] Ticket validation + Add to ticket-list
    
    @Override
    public boolean startServer()
    {
        //
        if (super.startServer())
        {
            // GameLijst Leeggooien
            // Dit is nodig bij bijv. een server-restart.
            synchronized (m_Games)
            {
                m_Games.clear();
            }
            try
            {
                m_Gateway.SetGameServer(this);
                HostObject("Gateway", m_Gateway);
            }
            catch (RemoteException ex)
            {
                ex.printStackTrace();
            }
            catch (AlreadyBoundException ex)
            {
                ex.printStackTrace();
            }
            return true;
        }
        return false;
    }
    
    /**
     * Valideert een ticket van een gebruiker.
     * @param ticket 
     * @return
     * @throws Exception
     */
    public int validateTicket(ITicket ticket)
            throws Exception
    {
        int result = ErrorCodes.ERROR_UNKNOWN;
        Exception ex = null;
        synchronized (m_Database)
        {
            try
            {
                result = m_Database.checkToken(ticket);
            }
            catch (Exception e)
            {
                ex = e;
            }
        }
        if (ex != null)
             throw ex;
        return result;
    }
    
    /**
     * Hiermee wordt een lege Game aangemaakt en geregistreerd op de
     * GameServer.
     * @param name gamenaam
     * @param maxplayers
     * @param mapid
     * @param gametime 
     * @param turntime 
     * @param armysize 
     * @return informatie over de aangemaakte Game
     * @throws RemoteException
     * @throws Exception
     */
    public ActionResult<IGameInfo> createGame(String name, int maxplayers, int mapid, int gametime, int turntime, int armysize)
            throws RemoteException, Exception
    {
        int result = ErrorCodes.ERROR_NOERROR;
        // Validate maximum players:
        if (maxplayers > 8 || maxplayers <= 0
	    || armysize <= 0 || name == null || name.trim().equals("")
	    || gametime < 1)
	{
	    result = ErrorCodes.ERROR_CREATE_GAME_INVALID_RULES;
            return null;
	}
        
        IGame _game = null;
        Exception ex = null;
        synchronized(m_Games)
        {
            try
            {
                Iterator<IGame> it = m_Games.iterator();
                if (m_Games.size() > 0)
                {
                    // Check if game already exists
                    while(it.hasNext())
                    {
                        IGame game = it.next();
                        if (game.getName().equals(name))
                        {
                            // Game Exists, don't look further
                            result = ErrorCodes.ERROR_GAME_ALREADY_EXISTS;
                            break;
                        }
                    }
                }

                if (result == ErrorCodes.ERROR_NOERROR)
                {
                    // Game didn't exist, so create it.
                    _game = new Game(this, name, maxplayers, m_Database.getMap(mapid), gametime, turntime, armysize);
                    m_Games.add(_game);
                }
            }
            catch (Exception e)
            {
                result = ErrorCodes.ERROR_UNKNOWN;
                ex = e;
            }
        }
        if (ex != null)
            throw ex;
        if (result != ErrorCodes.ERROR_NOERROR)
            return new ActionResult<IGameInfo>(result, null);
        
        // Notify Other Players who are in GameList
        ex = null;
        IGameInfo nfo = _game.getInfo();
        return new ActionResult<IGameInfo>(result, nfo);
    }

    /**
     * Geeft een lijst van geregistreerde Games terug.
     * @return geregistreerde Games
     * @throws RemoteException
     * @throws Exception
     */
    public List<IGame> getGames() throws RemoteException, Exception
    {
        List<IGame> games = new ArrayList<IGame>();
        Exception ex = null;
        synchronized(m_Games)
        {
            try
            {
                Iterator<IGame> it = m_Games.iterator();
                while(it.hasNext())
                {
                    games.add(it.next());
                }
            }
            catch (Exception e)
            {
                ex = e;
            }
        }
        if (ex != null)
            throw ex;
        return Collections.unmodifiableList(games);
    }

    /**
     * Geeft een lijst van informatie over geregistreerde Games terug.
     * @return informatie over geregistreerde Games
     * @throws RemoteException
     * @throws Exception
     */
    public List<IGameInfo> getGameInfos() throws RemoteException, Exception
    {
        List<IGameInfo> games = new ArrayList<IGameInfo>();
        Exception ex = null;
        synchronized(m_Games)
        {
            try
            {
                Iterator<IGame> it = m_Games.iterator();
                while(it.hasNext())
                {
                    IGame game = it.next();
                    if (((Game)game).getState() == GameState.GAME_WAITINGFORPLAYERS)
                        games.add(game.getInfo());
                }
            }
            catch (Exception e)
            {
                ex = e;
            }
        }
        if (ex != null)
            throw ex;
        return Collections.unmodifiableList(games);
    }

    /**
     * Gereserveerd voor als Resources moeten worden weggegooid.
     * @throws NoSuchObjectException
     */
    public void dispose() throws NoSuchObjectException
    {
        stopServer();
    }

    /**
     * Geeft het IPadres van de GameServer terug.
     * @return GameServer-IPadres
     * @throws java.net.UnknownHostException
     * @throws RemoteException
     */
    public String getIP() throws java.net.UnknownHostException, RemoteException
    {
        // Misschien dat deze eruit kan
        // anders haalt deze IP via java.net.

        // Vraag IPAdres op van de lokale machine
        InetAddress serverIP = InetAddress.getLocalHost();
        return serverIP.getHostAddress();
    }

    /**
     * Geeft de port van de GameServer terug.
     * @return GameServer-port
     * @throws RemoteException
     */
    public int getPort() throws RemoteException
    {
        return m_Port;
    }

    /**
     * Zoekt naar een game in de gamelijst d.m.v. informatie over de game.
     * @param info informatie over de game
     * @return game
     * @throws Exception
     */
    protected IGame findGame(IGameInfo info) throws Exception
    {
        IGame game = null;
        Exception ex = null;
        synchronized(m_Games)
        {
            try
            {
                //NOTE: Games zijn voilatile, dus we moeten ze opzoeken.
                Iterator<IGame> it = m_Games.iterator();
                while(it.hasNext())
                {
                    IGame _game = it.next();
                    if (_game.getName().equals(info.getName()))
                    {
                        game = _game;
                        break;
                    }
                }
            }
            catch (Exception e)
            {
                ex = e;
            }
        }
        if (ex != null)
            throw ex;
        return game;
    }
    
    /**
     * Zoekt naar een game in de gamelijst d.m.v. informatie over de game.
     * @param name 
     * @return game
     * @throws Exception
     */
    protected IGameInfo findGame(String name) throws Exception
    {
        IGame game = null;
        Exception ex = null;
        synchronized(m_Games)
        {
            try
            {
                //NOTE: Games zijn voilatile, dus we moeten ze opzoeken.
                Iterator<IGame> it = m_Games.iterator();
                while(it.hasNext())
                {
                    IGame _game = it.next();
                    if (_game.getName().equals(name))
                    {
                        game = _game;
                        break;
                    }
                }
            }
            catch (Exception e)
            {
                ex = e;
            }
        }
        if (ex != null)
            throw ex;
        
        // NOTE: GameInfo stuurt de klasse Game niet mee, want
        // deze is transient!
        if (game != null)
            return new GameInfo((Game)game);
        return null;
    }
    
    /**
     * Speler joint een game d.m.v. een ticket en gameinformatie.
     * @param info informatie over welke game hij joint
     * @param ticket ticket van de speler
     * @param callback
     * @return gamesessie
     * @throws RemoteException Remote Error
     * @throws Exception Generieke Error
     */
    public ActionResult<ISession> join(IGameInfo info, ITicket ticket, IClientService callback) throws RemoteException, Exception
    {
        int result = m_Database.checkToken(ticket);
        if (result != ErrorCodes.ERROR_NOERROR)
            return new ActionResult<ISession>(result, null);
        IGame game = findGame(info);
        return game.join(ticket, callback);
    }
    
    /**
     * Sluit een bestaand spel.
     * @param game bestaand spel.
     * @throws Exception
     */
    public void closeGame(IGame game) throws Exception
    {
        // Notify Other Players who are in GameList
        Exception ex = null;
        synchronized (m_Games)
        {
            try
            {
		((Game)game).dispose();
                m_Games.remove(game);
            }
            catch (Exception exc)
            {
                ex = exc;
            }
        }
        if (ex != null)
            throw ex;
    }
    
}